# -*- coding: utf-8 -*-
"""
Small XML to dictionary serializer with simple disk cache
"""

from xml.sax.handler import ContentHandler
from xml.sax.saxutils import XMLGenerator
from xml.sax.xmlreader import XMLReader
from xml.sax import make_parser
import binascii, struct
from os.path import getmtime, isfile

try:
	import cPickle as pickle
except ImportError:
	import pickle

def load(path, cache_path=''):
	res = False
	cache_path = cache_path or path+".cache"
	modified = getmtime(path)
	f = file(path, 'rb')
	cache_ok = False
	cache = None
	crc = None
	if(isfile(cache_path)):
		cache = file(cache_path, 'r+b')
		data = cache.read(8)
		cache_sum, cache_time = struct.unpack("LL", data)
		cache_ok = False
		if(modified != cache_time): # File modified by date
			# Check file CRC
			string = "1"
			crc = binascii.crc32(string)
			while(string):
				string = f.read(4)
				crc = binascii.crc32(string, crc)
			if(crc == cache_sum):
				cache_ok = True
		else:
			cache_ok = True
	if(cache_ok):
		data = ""
		for line in cache:
			data += line
		res = pickle.loads(data)
	else:
		f.seek(0)
		res = parse(f)
		if(not cache):
			cache = file(cache_path, 'w+b')
		if(not crc):
			f.seek(0)
			string = "1"
			crc = binascii.crc32(string)
			while(string):
				string = f.read(4)
				crc = binascii.crc32(string, crc)
		cache.seek(0)
		cache.write(struct.pack("LL", crc, modified))
		cache.write(pickle.dumps(res))
	cache and cache.close()
	f.close()
	return res

def parse(stream):
	"""
	Convert XML to dict using XMLDict class
	"""
	p = make_parser()
	h = Xml2Py()
	p.setContentHandler(h)
	p.parse(stream)
	return h.main[0]

def unparse(stream, d):
	p=Py2Xml()
	p.setContentHandler(XMLGenerator(stream, 'utf-8'))
	p.parse(d)
	return stream

def walk_elements(x, elem):
    if(isinstance(x, tuple) and elem):
    	return elem(x)
    else:
    	return None

def walk(x, chars, elem):
	    if( isinstance(x, basestring) and chars):
	    	return chars(x)
	    elif(isinstance(x, tuple) and elem):
	    	return elem(x)

class Xml2Py(ContentHandler):
	"""SAX Handler class which converts an XML
	file to a corresponding Python structure
	"""

	def __init__(self):
		self.main = []
		self.curr=self.main
		self.stack=[]

	def startElement(self, name, attrs):
		""" Start element handler """
		unit = ( name, dict(attrs), [] ) # Create a tag unit
		self.stack.append(self.curr)
		self.curr.append(unit)
		self.curr = unit[2]

	def endElement(self, name):
		""" End element handler """
		self.curr = self.stack.pop()

	def characters(self, content):
		""" Character handler """
		if ( isinstance(self.curr, basestring) ):
			self.curr += content
		else:
			self.curr.append(content)


class Py2Xml(XMLReader):
	"""A parser for Python dictionary representing it in
	XML using SAX interfaces """

	def _chars(self, x):
		self._cont_handler.characters(x)
	def _elem(self, x):
		self._cont_handler.startElement(x[0], x[1])
		[walk(e, self._chars, self._elem) for e in x[2]]
		self._cont_handler.endElement(x[0])
	def parse(self, main):
		" Parse Method "
		self._cont_handler.startDocument()
		walk(main, self._chars, self._elem)
		self._cont_handler.endDocument()

class walker:
	"""
	A class to process memory-mapped xml data.
	Provides a minimalistic xml path support to avoid using "heavy" XPath
	modules, that we don't need at all.
	"""
	def __init__(self, d):
		self.conf = d
		self.stack = []
	def _err(self, text):
		raise error.Config(text)

	def mapToDictOnly(self, func, array):
		a = []
		for i in array:
			if(isinstance(i, dict)):
				func(i) and a.append(i)
		return a

	def getProperty(self, dic, name, property):
		return 	(
				(dic.has_key(name) and dic[name][0].get(property))
				or
				False
				)

	def walk(self, path):
		"""
		Walks path starting from the beginning of the dictionary
		"""
		self.stack = []
		self.stack.append(self.conf)
		res = self.recursive(self._walkPath, path=path)
		return self.stack[-1]

	def findInChildren(self, node, fFind, **args):
		"""
		Walks node non-text children applying fFind() function until it returns False
		You can supply additional arguments to fFind through args, if needed.
		"""
		return reduce(lambda x, y: (
					x
					or
					(type(y) == dict and fFind(y, **args) and (self.stack.append(y) or True) and y)
					or
					False
					), node[node.keys()[0]][1], False
					)

	def recursive(self, fCheck, **args):
		"""
		Applies fCheck to children and goes recursively to children of matched children
		"""
		return	(
				len(self.stack) > 0 # We have an element in stack
				and
				self.findInChildren(self.stack[-1], fCheck, **args)
				and
				self.recursive(fCheck, **args)
				)

	def _walkPath(self, d, path):
		return (
			( len(path) > 0 )
			and
			( d.has_key(path[0]) ) # Check the instance name
			and
			(path.pop(0) and True)
			)
